﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using KaupischITC.ScanWIA;
using SampleApp.Properties;
using WIA;
using System.Linq;

namespace SampleApp
{
	/// <summary>
	/// Stellt ein Fenster zum Scannen und Speichern von Dokumenten dar
	/// </summary>
	public partial class ScanDialog : Form
	{
		/// <summary>
		/// Stellt Informationen über eine Scan-Quelle bereit
		/// </summary>
		private class ScanSource
		{
			public string Name { get; set; }
			public DocumentHandlingSelect DocumentHandlingSelect { get; set; }
		}


		/// <summary>
		/// Wird ausgelöst, wenn auf die OK-Schaltfläche geklickt wurde
		/// </summary>
		public EventHandler ButtonOkClicked;

		/// <summary>
		/// Wird ausgelöst, wenn ein Scanvorgang abgeschlossen wurde
		/// </summary>
		public EventHandler<ScanFinishedEventArgs> ScanFinished;
		public class ScanFinishedEventArgs : EventArgs
		{
			/// <summary> Gibt an, ob der Scanvorgang gültig ist (bei false kann nicht OK geklickt werden) </summary>
			public bool AcceptScan { get; set; }
		}

		/// <summary>
		/// Gibt an, ob sofort nach dem Anzeigen des Fenster gescannt werden soll, oder legt dieses fest
		/// </summary>
		public bool InstantScan { get; set; }


		/// <summary>
		/// Gibt die Funktion zum Berechnen der Pixel-Helligkeit für die benutzerdefinierte Schwellwertfunktion an
		/// </summary>
		public Func<int,int,int,int> GetCustomThresholdPixelTotal { get; set; }


		/// <summary>
		/// Gibt den Schwellwert für den Scanvorgang an oder legt diesen fest
		/// </summary>
		public int Threshold
		{
			get { return this.trackBarThreshold.Value; }
			set
			{
				if (value>this.trackBarThreshold.Minimum && value<this.trackBarThreshold.Maximum)
					this.trackBarThreshold.Value = value;
			}
		}

		/// <summary>
		/// Gibt die Auflösung für den Scanvorgang an oder legt diese fest
		/// </summary>
		public int Resolution { get; set; }

		/// <summary>
		/// Gibt das WIA-Gerät an, mit dem gescannt werden soll, oder legt dieses fest
		/// </summary>
		public ScannerDevice ScannerDevice
		{
			get { return this.scannerDevice; }
			set
			{
				// Proxies zum einfachen Zugriff auf die WIA-Eigenschaften erstellen
				this.scannerDevice = value;

				// die verfügbaren Dokumentenquellen auflisten
				List<ScanSource> scanSources = new List<ScanSource>();
				if ((this.ScannerDevice.DeviceSettings.DocumentHandlingCapabilities & DocumentHandlingCapabilities.Feed)>0)
					scanSources.Add(new ScanSource { Name = "Einzug",DocumentHandlingSelect = DocumentHandlingSelect.Feeder });
				if ((this.ScannerDevice.DeviceSettings.DocumentHandlingCapabilities & DocumentHandlingCapabilities.Dup)>0)
					scanSources.Add(new ScanSource { Name = "Einzug (beidseitig)",DocumentHandlingSelect = DocumentHandlingSelect.Duplex });
				if ((this.ScannerDevice.DeviceSettings.DocumentHandlingCapabilities & DocumentHandlingCapabilities.Flat)>0)
					scanSources.Add(new ScanSource { Name = "Flachbett",DocumentHandlingSelect = DocumentHandlingSelect.Flatbed });
				this.comboBoxSource.DataSource = scanSources;
				this.comboBoxSource.DisplayMember = "Name";
				this.comboBoxSource.ValueMember = "DocumentHandlingSelect";
			}
		}
		private ScannerDevice scannerDevice;

		/// <summary>
		/// Gibt die Scan-Quelle an oder legt diese fest.
		/// </summary>
		public DocumentHandlingSelect DocumentHandlingSelect
		{
			get { return (DocumentHandlingSelect)this.comboBoxSource.SelectedValue; }
			set { this.comboBoxSource.SelectedValue = value; }
		}

		/// <summary>
		/// Gibt die Farbeinstellung des Scanvorgangs an oder legt diesen fest
		/// </summary>
		public CurrentIntent CurrentIntent
		{
			get { return (CurrentIntent)this.comboBoxImageType.SelectedValue; }
			set { this.comboBoxImageType.SelectedValue = value; }
		}

		/// <summary>
		/// Gibt die Größe des gescannten Dokuments an (in Zoll) oder legt diese fest
		/// </summary>
		public SizeF DocumentSize
		{
			get { return (SizeF)this.comboBoxDocumentSize.SelectedValue; }
			set { this.comboBoxDocumentSize.SelectedValue = value; }
		}

		/// <summary>
		/// Gibt die Größe des gescannten Dokuments an (in Zoll) oder legt diese fest
		/// </summary>
		public string DocumentSizeName
		{
			get { return (string)this.comboBoxDocumentSize.Text; }
			set { this.comboBoxDocumentSize.Text = value; }
		}


		/// <summary>
		/// Gibt das gescannte aber ggf. nachbearbeitete Bild der Vorderseite zurück
		/// </summary>
		public Image FrontImage
		{
			get { return this.pictureBoxFront.Image; }
			private set { this.pictureBoxFront.Image = value; }
		}

		/// <summary>
		/// Gibt das gescannte aber ggf. nachbearbeitete Bild der Rückseite zurück (falls Duplex-Scan)
		/// </summary>
		public Image BackImage
		{
			get { return this.pictureBoxBack.Image; }
			private set { this.pictureBoxBack.Image = value; }
		}



		/// <summary>
		/// Erstellt ein neues Fenster zum Scannen und Speichern von Verordnungen
		/// </summary>
		/// <param name="belegID">die Belegnummer des Belegs, zu der die Verordnung gescannt und gespeichert werden soll.</param>
		public ScanDialog()
		{
			this.Font = SystemFonts.MessageBoxFont;
			this.InitializeComponent();
			this.Size = Settings.Default.ScanDialogSize;
			this.Threshold = Settings.Default.ScanThreshold;

			// Standardeinstellung für Auflösung und Scanner-Gerät
			this.Resolution = 200;
			try { this.ScannerDevice = WiaDevice.GetFirstScannerDevice().AsScannerDevice(); }
			catch (COMException comException) { MessageBox.Show(WiaException.GetMessageFromComException(comException),"Fehler",MessageBoxButtons.OK,MessageBoxIcon.Error); }

			// Auswahlmöglichkeiten für Farbschema
			this.comboBoxImageType.DataSource = new[] 
			{ 
				new { Name = "schwarzweiß", CurrentIntent = CurrentIntent.ImageTypeText },
				new { Name = "graustufen", CurrentIntent = CurrentIntent.ImageTypeGrayscale },
				new { Name = "farbig", CurrentIntent = CurrentIntent.ImageTypeColor },
			};
			this.comboBoxImageType.DisplayMember = "Name";
			this.comboBoxImageType.ValueMember = "CurrentIntent";

			// Auswahlmöglichkeiten für Papiergrößen
			this.comboBoxDocumentSize.DataSource = new[] 
			{ 
				new { Name = "A6 (quer)", SizeF = new SizeF(ScanDialog.MmToInch(148),ScanDialog.MmToInch(105)) },
				new { Name = "A5", SizeF = new SizeF(ScanDialog.MmToInch(148),ScanDialog.MmToInch(210)) },
				new { Name = "A4", SizeF = new SizeF(ScanDialog.MmToInch(210),ScanDialog.MmToInch(297)) },
			};
			this.comboBoxDocumentSize.DisplayMember = "Name";
			this.comboBoxDocumentSize.ValueMember = "SizeF";
		}


		/// <summary>
		/// Wenn das Fenster angezeigt wird
		/// </summary>
		private void OnFormScanShown(object sender,EventArgs e)
		{
			if (this.InstantScan)
				this.PerformScan();
		}


		/// <summary>
		/// Scannt die Verordnung
		/// </summary>
		public void PerformScan()
		{
			// UI in den Beschäftigt-Modus versetzen
			this.UseWaitCursor = true;
			this.buttonOK.Enabled = false;
			this.buttonRetry.Enabled = false;
			this.buttonRetry.Text = "Wiederholen";
			this.labelScanHint.Visible = false;
			this.trackBarThreshold.Enabled = false;
			this.panelProgress.Visible = true;
			this.pictureBoxFront.Image = null;
			this.pictureBoxBack.Image = null;
			DocumentHandlingSelect documentHandlingSelect = this.DocumentHandlingSelect;
			CurrentIntent currentIntent = this.CurrentIntent;
			SizeF documentSize = this.DocumentSize;

			int threshold = this.Threshold;
			new Thread((ThreadStart)delegate
			{
				try
				{
					// Einstellungen für das Scanner-Gerät
					this.ScannerDevice.DeviceSettings.DocumentHandlingSelect = documentHandlingSelect;
					this.ScannerDevice.DeviceSettings.Pages = (this.ScannerDevice.DeviceSettings.DocumentHandlingSelect==DocumentHandlingSelect.Duplex) ? 2 : 1;

					// Einstellungen für das Scandokument
					this.ScannerDevice.PictureSettings.CurrentIntent = currentIntent;
					this.ScannerDevice.PictureSettings.HorizontalResolution = this.Resolution;
					this.ScannerDevice.PictureSettings.VerticalResolution = this.Resolution;
					this.ScannerDevice.PictureSettings.HorizontalExtent = (int)(documentSize.Width*this.Resolution);
					this.ScannerDevice.PictureSettings.VerticalExtent = (int)(documentSize.Height*this.Resolution);
					this.ScannerDevice.PictureSettings.Threshold = threshold;

					// Scanvorgang durchführen
					List<Image> images = this.ScannerDevice.PerformScan().ToList();

					// Bild in Fenster anzeigen
					this.Invoke((MethodInvoker)delegate
					{
						this.FrontImage = images[0];
						this.BackImage = (images.Count>1) ? images[1] : null;

						this.SetDuplexDisplayOrientation();

						this.splitContainer.Panel2Collapsed = (this.BackImage==null);
						this.panelProgress.Visible = false;

						ScanFinishedEventArgs eventArgs = new ScanFinishedEventArgs { AcceptScan = true };
						if (this.ScanFinished!=null)
							this.ScanFinished(this,eventArgs);
						this.buttonOK.Enabled = eventArgs.AcceptScan;
					});
				}
				catch (COMException ex)
				{
					string message = WiaException.GetMessageFromComException(ex);
					MessageBox.Show(message,"Scan-Fehler",MessageBoxButtons.OK,MessageBoxIcon.Error);
				}
				catch (Exception ex)
				{
					MessageBox.Show(ex.ToString(),"Fehler",MessageBoxButtons.OK,MessageBoxIcon.Error);
				}

				// UI in den Bereit-zum-SpeichernOderWiederholen-Modus versetzen
				this.Invoke((MethodInvoker)delegate
				{
					this.UseWaitCursor = false;
					this.buttonRetry.Enabled = true;
					this.trackBarThreshold.Enabled = true;
					this.panelProgress.Visible = false;
				});

			}) { IsBackground = true }.Start();
		}


		/// <summary>
		/// Wenn die Größe des Fensters geändert wird, die Ausrichtung der Duplex-Anzeige so einstellen, dass der Platz optimal genutzt wird
		/// </summary>
		private void SplitContainer_SizeChanged(object sender,EventArgs e)
		{
			this.SetDuplexDisplayOrientation();
		}

		/// <summary>
		/// Ordnet (bei Duplex-Scan) die Anzeige für Vorder- & Rückseite so an (übereinander oder untereinander), dass der verfügbare Platz maximal ausgenutzt wird
		/// </summary>
		private void SetDuplexDisplayOrientation()
		{
			if (this.FrontImage!=null && this.BackImage!=null)
			{
				double width = this.panelPictures.Width / (double)this.FrontImage.Width;
				double heigth = this.panelPictures.Height / (double)this.FrontImage.Height;
				this.splitContainer.Orientation = (width<heigth) ? Orientation.Horizontal : Orientation.Vertical;
				this.splitContainer.SplitterDistance = ((this.splitContainer.Orientation==Orientation.Horizontal) ? this.splitContainer.Height : this.splitContainer.Width)/2;
			}
		}



		/// <summary>
		/// Rechnet Millimeter in Zoll um
		/// </summary>
		/// <param name="mm">Millimeter</param>
		/// <returns>Millimeter umgerechnet in Zoll</returns>
		public static float MmToInch(int mm)
		{
			return 0.03937f*mm;
		}



		/// <summary>
		/// Beim Klick auf "OK" die gescannte Verordnung als TIFF und JPEG speichern
		/// </summary>
		private void OnButtonOkClicked(object sender,EventArgs e)
		{
			this.UseWaitCursor = true;
			try
			{
				if (this.ButtonOkClicked!=null)
					this.ButtonOkClicked(sender,e);

				this.DialogResult = DialogResult.OK;
				this.Close();
			}
			catch (Exception exception)
			{
				MessageBox.Show(exception.Message,"Fehler",MessageBoxButtons.OK,MessageBoxIcon.Error);
				this.UseWaitCursor = false;
			}
		}


		/// <summary>
		/// Beim Klick auf "Wiederholen" nochmal scannen
		/// </summary>
		private void OnButtonRetryClicked(object sender,EventArgs e)
		{
			this.PerformScan();
		}


		/// <summary>
		/// Beim Klick auf "Abbrechen"
		/// </summary>
		private void OnButtonCancelClicked(object sender,EventArgs e)
		{
			this.DialogResult = DialogResult.Cancel;
			this.Close();
		}


		/// <summary>
		/// Wenn das Fenster geschlossen wird
		/// </summary>
		private void OnScanDialogClosing(object sender,FormClosingEventArgs e)
		{
			Settings.Default.ScanDialogSize = this.Size;
			Settings.Default.ScanThreshold = this.Threshold;
			Settings.Default.Save();
		}
	}
}
